package com.membership.cloud.service;

import com.membership.cloud.exception.InvalidItemException;
import com.membership.cloud.exception.InvalidItemException;
import com.membership.cloud.exception.ServerTechnicalException;
import com.membership.cloud.exception.UnauthenticatedException;
import com.membership.cloud.exception.UnauthorizedException;
import com.membership.cloud.jpa.domain.StoreUser;
import com.membership.cloud.jpa.repository.StoreUserRepository;
import com.membership.cloud.web.model.UserResponse;
import com.membership.cloud.web.model.UserSessionInfo;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;

/**
 * Created by hiephn on 2014/07/14.
 */
@Service
public class SecurityService {
    @Autowired
    private UserSessionInfo userSessionInfo;
    @Autowired
    private StoreUserRepository userRepo;

    /**
     * Used to retrive current user and check login.
     * @return current user
     * @throws UnauthorizedException if user is not yet login
     */
    public UserSessionInfo retrieveCurrentUser() throws UnauthorizedException {
        if (StringUtils.isEmpty(userSessionInfo.getName())) {
            throw new UnauthorizedException("User has no session. Please login.");
        }
        return userSessionInfo;
    }

    /**
     * Check if current user is one of target roles
     * @throws UnauthorizedException if user is not yet login
     * @throws UnauthenticatedException if user is not one of target roles
     */
    public void validateCurrentRole(UserSessionInfo.Role... roles)
            throws UnauthenticatedException, UnauthorizedException {
        // Check login
        this.retrieveCurrentUser();
        for (UserSessionInfo.Role role : userSessionInfo.getRoles()) {
            for (UserSessionInfo.Role targetRole : roles) {
                if (role.equals(targetRole)) {
                    return;
                }
            }
        }
        String errorMsg = userSessionInfo.getName() + " is not ";
        for (UserSessionInfo.Role targetRole : roles) {
            errorMsg += targetRole.getValue() + ", ";
        }
        throw new UnauthenticatedException(errorMsg.substring(0, errorMsg.length() - 2));
    }

    /**
     * Check if current user is admin
     * @throws UnauthorizedException if user is not yet login
     * @throws UnauthenticatedException if user is not admin
     */
    public void validateCurrentAdmin()
            throws UnauthorizedException, UnauthenticatedException {
        // Check login
        this.retrieveCurrentUser();
        for (UserSessionInfo.Role role : userSessionInfo.getRoles()) {
            if (role.equals(UserSessionInfo.Role.ADMIN)) {
                return;
            }
        }
        throw new UnauthenticatedException();
    }

    /**
     * Check if current user is manager
     * @throws UnauthorizedException if user is not yet login
     * @throws UnauthenticatedException if user is not manager
     */
    public void validateCurrentManager()
            throws UnauthorizedException, UnauthenticatedException {
        // Check login
        this.retrieveCurrentUser();
        for (UserSessionInfo.Role role : userSessionInfo.getRoles()) {
            if (role.equals(UserSessionInfo.Role.MANAGER)) {
                return;
            }
        }
        throw new UnauthenticatedException(userSessionInfo.getName() + " is not an manager");
    }

    /**
     * Check if current user is staff
     * @throws UnauthorizedException if user is not yet login
     * @throws UnauthenticatedException if user is not staff
     */
    public void validateCurrentStaff()
            throws UnauthorizedException, UnauthenticatedException {
        // Check login
        this.retrieveCurrentUser();
        for (UserSessionInfo.Role role : userSessionInfo.getRoles()) {
            if (role.equals(UserSessionInfo.Role.STAFF)) {
                return;
            }
        }
        throw new UnauthenticatedException(userSessionInfo.getName() + " is not an staff");
    }

    public UserResponse authorize(String authorizeHeader)
            throws InvalidItemException, UnauthorizedException, ServerTechnicalException {

        this.signOutCurrentUser();
        if (!StringUtils.contains(authorizeHeader, "Basic ")) {
            throw new InvalidItemException("Invalid credential information");
        }
        authorizeHeader = StringUtils.removeStart(authorizeHeader, "Basic ");
        String decoded = new String(Base64.getDecoder().decode(authorizeHeader));
        String[] items = decoded.split(":");
        if (items.length != 2) {
            throw new InvalidItemException("Invalid credential information");
        }
        String username = items[0];
        String password = items[1];

        StoreUser storeUser = userRepo.findByNameAndPassword(username, this.hashSha(password));
        if (storeUser == null) {
            throw new UnauthorizedException("Username or Password not correct");
        }

        userSessionInfo.setData(storeUser);
        UserResponse userResponse = new UserResponse(storeUser);

        return userResponse;
    }

    public void signOutCurrentUser() {
        userSessionInfo.resetData();
    }


    /**
     * Hash the string using simple hash algorithm: SHA-1
     *
     * @param str
     * @return
     */
    protected String hashSha(String str) throws ServerTechnicalException {
        String generatedPassword = null;
        try {
            MessageDigest md = MessageDigest.getInstance("SHA-1");
            byte[] bytes = md.digest(str.getBytes());
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < bytes.length; i++) {
                sb.append(Integer.toString((bytes[i] & 0xff) + 0x100, 16)
                        .substring(1));
            }
            generatedPassword = sb.toString();
        } catch (NoSuchAlgorithmException e) {
            throw new ServerTechnicalException("Cannot hash string object.", e);
        }
        return generatedPassword;
    }
}
